﻿namespace JoinBox.MathEx;

using System;

public partial class Geometrist
{
    /// <summary>
    /// 直线方程求交点
    /// </summary>
    public static PointV IntersectWith(PointV p1, PointV p2, PointV p3, PointV p4)
    {
        var obj = IntersectWith(p1.X, p1.Y, p2.X, p2.Y, p3.X, p3.Y, p4.X, p4.Y);
        return new PointV(obj);
    }

    static bool Eq(double a, double b, double tolerance = 1e-6)
    {
        return System.Math.Abs(a - b) < tolerance;
    }

    /// <summary>
    /// 直线方程求交点
    /// </summary>
    static double[] IntersectWith(
       double x1, double y1,
       double x2, double y2,
       double x3, double y3,
       double x4, double y4)
    {
        GetSlope(x1, y1, x2, y2, x3, y3, x4, y4, out double a, out double b);

        double _x, _y;

        // L1或L2两直线可能其中一个有Y轴平行(垂直X轴)的
        if (Eq(x2, x1)) // L1垂直于x轴  则x=x1=x2,(x2 - x1)是0==a分母,a=Infinity正无穷
        {
            _x = x1;
            _y = b * x1 - b * x3 + y3;// 公式变换第一种
            return new double[] { _x, _y };
        }
        else if (Eq(x4, x3)) // L2垂直于x轴 则x=x3=x4,(x4 - x3)是0==b分母,b=Infinity正无穷
        {
            _x = x3;
            _y = a * _x - a * x1 + y1;// 公式变换第一种
            return new double[] { _x, _y };
        }

        // 两条直线都是非垂直状态

        /* 知道了点和斜率,那么两条点斜式方程联立.
           因为直线方程是一条参照线,两端无限延长,除非平行,否则必有交点.
           又因为交点是两条线的共同解,所以点斜式:line1和line2的y相减是0,y=k(_x-x1)+y1
           所以未知数y就相减去掉,剩下x,来求y.
           反之,也可以相减去掉x,来求y.
           [y=a(_x-x1)+y1] - [y=b(_x-x3)+y3] =0;
           [a*(_x-x1)+y1] - [b*(_x-x3)+y3] =0;
           [a*_x-a*x1+y1] - [b*_x-b*x3+y3] =0;
           a*_x-a*x1+y1 - b*_x+b*x3-y3 =0; 去括号,+-互变
           (a - b)* _x = 0 + a*x1 - y1 - b*x3 + y3; // 移项,+-互变
           _x = (a * x1 - y1 - b * x3 + y3) / (a - b);
        */
        _x = (a * x1 - y1 - b * x3 + y3) / (a - b);

        // 但是上面程序代码已经算了_x了,直接套入点斜式方程,偷懒...也可以通过公式计算
        /* y-y1=k*x-k*x1
           y-y1+k*x1=k*x
           (y-y1+k*x1)/k=x
           [(y-y1-a*x1)/a] - [(y-y3-b*x3)/b] =0; // 这是按照公式的方法
        */

        _y = a * _x - a * x1 + y1;  // 点斜式方程 y-y1=k(x-x1)

        return new double[] { _x, _y };
    }

    /// <summary>
    /// 获取两条线的斜率,和警告
    /// </summary>
    static void GetSlope(
        double x1, double y1,
        double x2, double y2,
        double x3, double y3,
        double x4, double y4,
        out double a, out double b)
    {
        // 因为求斜率需要用除法,分母可能为0,所以求斜率之前,
        // 需要两条线是否x轴平行或者y轴平行
        if (Eq(x2, x1) && Eq(x4, x3))
            throw new Exception("与y轴平行,两直线垂直,斜率不存在,无交点");
        if (Eq(y2, y1) && Eq(y4, y3))
            throw new Exception("与x轴平行,两直线水平,斜率为零,无交点");

        // 求斜率,分母为0并不报错,而是赋值成 Infinity
        a = (y2 - y1) / (x2 - x1);
        b = (y4 - y3) / (x4 - x3);
        if (Eq(a, b))
            throw new Exception("斜率一致,两直线斜着平行,无交点");
    }

    // 在点斜式上面使用了逻辑避让开垂直和水平的情况,那么一般式里面本身就容纳这个逻辑.
    // https://blog.csdn.net/yangtrees/article/details/7965983
    // 先判断直线不平行再调用此方法

    /// <summary>
    /// 求交点,直线方程一般式,平行无解
    /// </summary>
    /// <returns>交点</returns>
    public static PointV GetCrossPoint(PointV p1, PointV p2, PointV p3, PointV p4)
    {
        /* 直线方程一般式: Ax+By+C=0,推导:
         * 当x1!=x2,则斜率[(y2-y1)/(x2-x1)],点斜式方程: y-y1=k(x-x1)
         * y-y1     = [(y2-y1)/(x2-x1)]*(x-x1)                   ;高=斜率*底
         * y        = [(y2-y1)/(x2-x1)]*(x-x1)+y1                ;加法交换律,y=斜率*底+y1.
         * (x2-x1)y = {[(y2-y1)/(x2-x1)]*(x-x1)+y1}*(x2-x1)      ;两边同乘(x2-x1)
         *          = (y2-y1)/(x2-x1)*(x-x1)*(x2-x1) + y1(x2-x1) ;拆括号
         *          = (y2-y1)*(x-x1) + y1(x2-x1)                 ;约去两个相同(x2-x1)项
         *          = (y2-y1)x    -(y2-y1)x1  + y1(x2-x1)        ;这一步开始根据一般式的格式,将系数划分出来
         *          |            |-(x1y2-x1y1)+ x2y1-x1y1        ;简单运算
         *          |            |x2y1-x1y1-(x1y2-x1y1)          ;简单运算
         *          |            |x2y1-x1y1-x1y2+x1y1            ;简单运算
         * (x2-x1)y | (y2-y1)x   |x2y1-x1y2                      ;简单运算
         * -B       | A          |C                              ;格式
         * ------------------------------------------------------;保留系数
         * x1-x2    | y2-y1      |x2y1-x1y2                      ;写入到代码中
         * B        | A          |C                              ;直线方程一般式
         */
        var y1 = p2.Y - p1.Y;
        var x1 = p1.X - p2.X;
        var c1 = p2.X * p1.Y - p1.X * p2.Y;

        var y2 = p4.Y - p3.Y;
        var x2 = p3.X - p4.X;
        var c2 = p4.X * p3.Y - p3.X * p4.Y;

        /*  求交点就是联立方程:
         *  (A1x+B1y+C1)-(A2x+B2y+C2)=0,二者实际上就是联立方程组的叉积应用
         *  叉乘:依次用手指盖住每列,交叉相乘再相减,注意主副顺序
         *  x   y   z
         *  y1  x1  c1
         *  y2  x2  c2
         */
        var x = x1 * c2 - x2 * c1;// 主-副(左上到右下是主,左下到右上是副)
        var y = y2 * c1 - y1 * c2;// 副-主
        var z = y1 * x2 - y2 * x1;// 主-副,为0表示两直线重合
        var cp = new PointV();
        if (System.Math.Abs(z) > 1e-8)
            cp = new PointV(x / z, y / z);// z / z
        // 唉唉唉!!!这样Z不都是1了?直线方程是平面系,因此Z抹去,
        // 那么明明叉乘是可以满足XYZ的推导,恰恰这个时候告诉你Z抹去了,
        // 那是不是代表说,直线方程 上面少了参数?然后就可以描述成 空间系方程?
        // 所以就延伸出 空间直线方程一般式: Ax+By+Cz+D=0.
        return cp;
    }

    /// <summary>
    /// 求线段AB与CD的交点P的坐标
    /// </summary>
    /// <param name="line1SpA"></param>
    /// <param name="line1EpB"></param>
    /// <param name="line2SpC"></param>
    /// <param name="line2EpD"></param>
    /// <returns></returns>
    // public static PointV GetIntersection(
    //    PointV line1SpA, PointV line1EpB,
    //    PointV line2SpC, PointV line2EpD)
    // {
    //    // POJ1408 两条线段求交点+叉乘求几何面积+枚举
    //    var areaofAbd = GetTriangleArea(line1SpA, line1EpB, line2EpD);
    //    var areaofAbc = GetTriangleArea(line1SpA, line2SpC, line1EpB);
    //    var xx = (areaofAbd * (line2SpC.X) + areaofAbc * (line2EpD.X)) / (areaofAbd + areaofAbc);
    //    var yy = (areaofAbd * (line2SpC.Y) + areaofAbc * (line2EpD.Y)) / (areaofAbd + areaofAbc);
    //    return new PointV(xx, yy);
    // }


}
